package com.dtflys.easyel.compile;

import com.dtflys.easyel.exception.EasyElException;


import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * @author gongjun[jun.gong@thebeastshop.com]
 * @since v1.0.0
 */
public class EasyElSource {

    public final static int READ_WINDOW_SIZE = 1000;
    
    public final static int SOURCE_TEXT = 0x01;
    public final static int SOURCE_FILE = 0x02;

    private String sourceText;
    private Path sourceFilePath;
    private int sourceType;
    private String sourceName;
    private final boolean isScript;
    
    private int readWindowStartLine = 0;
    private int readWindowEndLine = 0;
    private List<EasyElSourceLine> readWindowLines = new ArrayList<>();
    
    private int currentLine = 0;

    private static Charset UTF8 = Charset.forName("UTF-8");
    
    
    private static String generateSourceNameOfScriptText(String text) {
        int idx = text.indexOf('\n');
        if (idx == -1) {
            return "'" + text + "'";
        }
        return "'" + text.substring(0, idx) + "'";
    }
    
    public EasyElSource(String text) {
        this(text, generateSourceNameOfScriptText(text));
    }

    public EasyElSource(String text, String sourceName) {
        this.sourceType = SOURCE_TEXT;
        this.sourceName = sourceName;
        this.sourceText = text;
        this.isScript = true;
    }

    public EasyElSource(File file) throws IOException {
        this.sourceType = SOURCE_FILE;
        this.sourceName = file.getName();
        this.sourceFilePath = file.toPath();
        this.isScript = false;
    }

    public int getSourceType() {
        return sourceType;
    }

    public String getSourceName() {
        return sourceName;
    }
    
    public InputStream getInputStream() throws IOException {
        if (sourceType == SOURCE_TEXT) {
            return new ByteArrayInputStream(sourceText.getBytes(UTF8));
        } else if (sourceType == SOURCE_FILE) {
            return Files.newInputStream(sourceFilePath);
        }
        return null;
    }

    public List<EasyElSourceLine> readLineList(int startLine, int endLine) {
        try (InputStream in = getInputStream()) {
            BufferedReader reader = new BufferedReader(new InputStreamReader(in, UTF8));
            int lineNum = 1;
            List<EasyElSourceLine> lineList = new ArrayList<>();
            int readEndLine = endLine + 1;
            for (; ; ) {
                try {
                    // skip lines
                    if (lineNum < startLine) {
                        lineNum++;
                        reader.readLine();
                    } else {
                        String line = reader.readLine();
                        if (line == null) {
                            break;
                        }
                        
                        if (lineNum <= readEndLine) {
                            if (!lineList.isEmpty()) {
                                lineList.get(lineList.size() - 1).appendNL();
                            }
                            if (lineNum <= endLine) {
                                lineList.add(new EasyElSourceLine(lineNum, line));
                            }
                        } else {
                            break;
                        }
                        lineNum++;
                    }
                } catch (IOException e) {
                    throw new EasyElException("Failed read source at line " + lineNum);
                }
            }
            return lineList;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public List<EasyElSourceLine> readLineList(EasyElSourcePosition position) {
        return readLineList(position.getStartLine(), position.getEndLine());
    }
    
    public EasyElSourceLine readLine(int line) {
        if (readWindowStartLine < line && readWindowEndLine >= line) {
            return readWindowLines.get(line - readWindowStartLine);
        }
        List<EasyElSourceLine> lineList = readLineList(line, line);
        if (lineList.isEmpty()) {
            return null;
        }
        return readWindowLines.get(0);
    }
    
    private void refreshReadWindow() {
        int startLine = currentLine;
        int endLine = currentLine + READ_WINDOW_SIZE - 1;
        List<EasyElSourceLine> lines = readLineList(startLine, endLine);
        if (lines.isEmpty()) {
            readWindowLines = null;
        } else {
            readWindowStartLine = startLine;
            readWindowEndLine = startLine + lines.size() - 1;
            readWindowLines = lines;
        }
    }
    
    public EasyElSourceLine nextLine() {
        currentLine++;
        if (currentLine > readWindowEndLine) {
            refreshReadWindow();
        }
        if (readWindowLines == null || readWindowLines.isEmpty()) {
            currentLine--;
            return null;
        }
        return readWindowLines.get(currentLine - readWindowStartLine);
    }
    
    public EasyElSourceLine backLine() {
        currentLine--;
        if (currentLine < readWindowStartLine || currentLine > readWindowEndLine) {
            refreshReadWindow();
        }
        if (readWindowLines.isEmpty()) {
            currentLine--;
            return null;
        }
        return readWindowLines.get(currentLine - readWindowStartLine);
    }

    public String getSourceText() {
        return sourceText;
    }

    public Path getSourceFilePath() {
        return sourceFilePath;
    }

    public int getReadWindowStartLine() {
        return readWindowStartLine;
    }

    public int getReadWindowEndLine() {
        return readWindowEndLine;
    }

    public List<EasyElSourceLine> getReadWindowLines() {
        return readWindowLines;
    }

    public int getCurrentLine() {
        return currentLine;
    }

    public boolean isScript() {
        return isScript;
    }
}
